/*
 *
 *    Copyright (c) 2020 Project CHIP Authors
 *    Copyright (c) 2019 Google LLC.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

/**
 *    @file
 *          Defines the public interface for the Device Layer SoftwareUpdateManager object.
 */

#pragma once

#if CHIP_DEVICE_CONFIG_ENABLE_SOFTWARE_UPDATE_MANAGER

#include <core/CHIPTLV.h>
#include <platform/internal/CHIPDeviceLayerInternal.h>

namespace chip {
namespace DeviceLayer {

using namespace chip::TLV;

class SoftwareUpdateManagerImpl;

class SoftwareUpdateManager
{
    using ImplClass = SoftwareUpdateManagerImpl;

public:
    // ===== Members that define the public interface of the SoftwareUpdateManager

    enum State
    {
        kState_Idle                = 1,
        kState_ScheduledHoldoff    = 2,
        kState_PrepareQuery        = 3,
        kState_Query               = 4,
        kState_PrepareImageStorage = 5,
        kState_Download            = 6,
        kState_Install             = 7,

        kState_ApplicationManaged = 8,

        kState_MaxState
    };

    /**
     * API events generated by the \c SoftwareUpdateManager object.
     */
    enum EventType
    {
        /**
         *  Prepare ImageQuery message
         *
         *  Generated when a software update check has been triggered. Provides an opportunity
         *  for the application to supply product related information to the
         *  SofwareUpdate:ImageQuery message.
         */
        kEvent_PrepareQuery,

        /**
         *  Prepare meta-data for ImageQuery request
         *
         *  Provides an opportunity for the application to append additional meta-data
         *  to the SofwareUpdate:ImageQuery message if needed. Generated when implementation
         *  is ready to get meta-data from the application.
         */
        kEvent_PrepareQuery_Metadata,

        /**
         *  Error preparing an ImageQuery request
         *
         *  Generated when the implementation encounters an error while preparing to send out
         *  a software update query.
         */
        kEvent_QueryPrepareFailed,

        /**
         *  ImageQuery request has been sent
         *
         *  Informational event to signal that a SofwareUpdate:ImageQuery message has been sent.
         */
        kEvent_QuerySent,

        /**
         *  Software update is available
         *
         *  Generated when a SofwareUpdate:ImageQueryResponse is received in response to
         *  a query containing information of the available update.
         */
        kEvent_SoftwareUpdateAvailable,

        /**
         *  Fetch persisted state information for a partially downloaded image
         *
         *  Provides an opportunity for the application to disclose information
         *  of a partial image previously downloaded so that the download
         *  may be continued from the point where it last stopped. URI of the available
         *  software update is provided as an input parameter that the application can use
         *  to compare if the image being downloaded is the same as the partial image.
         *
         *  The application is expected to return the length of the partial image in the
         *  PartialImageLenInBytes output parameter.  The application can set the value
         *  of PartialImageLenInBytes to 0 to indicate that no partial image exists or
         *  that the URI of the partial image does not match.
         *
         *  The application may choose to ignore this event by passing it to the default
         *  event handler. If this is done, the system will always download the entirety
         *  of the available firmware image.
         */
        kEvent_FetchPartialImageInfo,

        /**
         *  Prepare for storage of a new image
         *
         *  Requests the application to perform any steps necessary to prepare local storage
         *  for the download of a new firmware image.  The application can use this, for example,
         *  to erase flash pages.
         *
         *  The PrepareImageStorage event is generated only in the case where a new firmware
         *  image is being downloaded.  When a previously interrupted download is resumed,
         *  PrepareImageStorage is not generated.
         *
         *  The application must signal completion of the prepare operation by calling the
         *  \c PrepareImageStorageComplete() method.  It may do this within the event callback
         *  itself, or at a later time.  If called from a task other than the chip task,
         *  the caller must hold the chip stack lock.
         *
         *  The application can choose to ignore the PrepareImageStorage event by passing it
         *  to the default event handler.  If this is done, the system automatically proceeds
         *  to the image download state.
         *
         *  To support resuming an interrupted download, the application should persist the
         *  image URI (supplied as an event parameter), and use this when handling subsequent
         *  FetchPartialImageInfo events.
         */
        kEvent_PrepareImageStorage,

        /**
         *  Image download has begun
         *
         *  Informational event to signal the start of an image download transaction.
         */
        kEvent_StartImageDownload,

        /**
         *  Store a block of image data
         *
         *  Generated whenever a data block is received from the file download server.
         *  Parameters included with this event provide the data and the length of the data.
         *
         *  To support resuming an interrupted download, the application should maintain a
         *  persistent count of the total number of image bytes stored, and use this value
         *  when handling subsequent FetchPartialImageInfo events.
         */
        kEvent_StoreImageBlock,

        /**
         *  Compute an image integrity check value
         *
         *  Requests the application to compute an integrity check value over the downloaded
         *  image. Generated once downloading is complete.
         */
        kEvent_ComputeImageIntegrity,

        /**
         *  Reset state of partially downloaded image
         *
         *  Requests the application to forget the persisted state associated with a downloaded
         *  image.  A ResetPartialImageInfo event is generated whenever a downloaded image fails
         *  its integrity check.  After a ResetPartialImageInfo event has been processed,
         *  subsequent FetchPartialImageInfo events should indicate that no partial image is
         *  available.
         *
         *  Note that, when handling the ResetPartialImageInfo event, the application is NOT
         *  required to clear image data itself, only the state information associated with the
         *  image (i.e. the URI and partial image length).
         *
         *  If the application does not support image download resumption, it may ignore this
         *  event by passing it to the default event handler.
         */
        kEvent_ResetPartialImageInfo,

        /**
         *  Image is ready to be installed
         *
         *  Informational event to signal that image is ready to be installed.
         *  Generated once an image passes the integrity check.
         */
        kEvent_ReadyToInstall,

        /**
         *  Begin image installation
         *
         *  Requests the application to being the process of installing a downloaded firmware
         *  image.
         */
        kEvent_StartInstallImage,

        /**
         *  Software update process finished
         *
         *  Generated when a software update check has finished with or without
         *  errors. Parameters included with this event provide the reason for failure
         *  if the attempt finished due to a failure.
         */
        kEvent_Finished,

        /**
         *  Check default event handling behavior.
         *
         *  Used to verify correct default event handling in the application.
         *
         *  Applications must NOT handle this event.
         */
        kEvent_DefaultCheck = 100,

    };

    /**
     *  When a software update is available, the application can chose one of
     *  the following actions as part of the SoftwareUpdateAvailable API event
     *  callback. The default action will be set to kAction_Now.
     */
    enum ActionType
    {
        /**
         *  Ignore the download completely. A kEvent_Finished API event callback will
         *  be generated with error CHIP_DEVICE_ERROR_SOFTWARE_UPDATE_CANCELLED if
         *  this option is selected and the retry logic will not be invoked.
         */
        kAction_Ignore,

        /**
         *  Start the download right away. A kEvent_FetchPartialImageInfo API event
         *  callback will be generated right after.
         */
        kAction_DownloadNow,

        /**
         *  Pause download on start. Scheduled software update checks (if enabled) will be suspended.
         *  State machine will remain in Download state. When ready, application can
         *  call the resume download API to proceed with download or call Abort to cancel.
         */
        kAction_DownloadLater,

        /**
         *  Allows application to manage the rest of the phases of software update such as
         *  download, image integrity validation and install. Software update manager
         *  state machine will move to the ApplicationManaged state. Scheduled software update checks (if enabled)
         *  will be suspended till application calls Abort or ImageInstallComplete API.
         */
        kAction_ApplicationManaged,
    };

    /**
     *  Incoming parameters sent with events generated directly from this component
     *
     */
    union InEventParam;

    /**
     *  Outgoing parameters sent with events generated directly from this component
     *
     */
    union OutEventParam;

    struct RetryParam
    {
        /**
         *  Specifies the retry attempt number.
         *  It is reset on a successful software update attempt.
         */
        uint32_t NumRetries;
    };

    typedef void (*EventCallback)(void * apAppState, EventType aEvent, const InEventParam & aInParam, OutEventParam & aOutParam);
    typedef void (*RetryPolicyCallback)(void * aAppState, RetryParam & aRetryParam, uint32_t & aOutIntervalMsec);

    CHIP_ERROR Abort();
    CHIP_ERROR CheckNow();
    CHIP_ERROR ImageInstallComplete(CHIP_ERROR aError);
    CHIP_ERROR PrepareImageStorageComplete(CHIP_ERROR aError);
    CHIP_ERROR SetEventCallback(void * aAppState, EventCallback aEventCallback);
    CHIP_ERROR SetQueryIntervalWindow(uint32_t aMinWaitTimeMs, uint32_t aMaxWaitTimeMs);

    bool IsInProgress();

    void SetRetryPolicyCallback(RetryPolicyCallback aRetryPolicyCallback);

    State GetState();

    static void DefaultEventHandler(void * apAppState, EventType aEvent, const InEventParam & aInParam, OutEventParam & aOutParam);

private:
    // ===== Members for internal use by the following friends.

    // friend class SoftwareUpdateManagerImpl;
    template <class>
    friend class Internal::GenericPlatformManagerImpl;

    CHIP_ERROR Init();

protected:
    // Construction/destruction limited to subclasses.
    SoftwareUpdateManager()  = default;
    ~SoftwareUpdateManager() = default;

    // No copy, move or assignment.
    SoftwareUpdateManager(const SoftwareUpdateManager &)  = delete;
    SoftwareUpdateManager(const SoftwareUpdateManager &&) = delete;
    SoftwareUpdateManager & operator=(const SoftwareUpdateManager &) = delete;
};

/**
 * Returns a reference to the public interface of the SoftwareUpdateManager singleton object.
 *
 * chip application should use this to access features of the SoftwareUpdateManager object
 * that are common to all platforms.
 */
extern SoftwareUpdateManager & SoftwareUpdateMgr();

/**
 * Returns the platform-specific implementation of the SoftwareUpdateManager singleton object.
 *
 * chip applications can use this to gain access to features of the SoftwareUpdateManager
 * that are specific to the selected platform.
 */
extern SoftwareUpdateManagerImpl & SoftwareUpdateMgrImpl();

} // namespace DeviceLayer
} // namespace chip

/* Include a header file containing the implementation of the SoftwareUpdateManager
 * object for the selected platform.
 */
#ifdef EXTERNAL_SOFTWAREUPDATEMANAGERIMPL_HEADER
#include EXTERNAL_SOFTWAREUPDATEMANAGERIMPL_HEADER
#else
#define SOFTWAREUPDATEMANAGERIMPL_HEADER <platform/SoftwareUpdateManagerImpl.h>
#include SOFTWAREUPDATEMANAGERIMPL_HEADER
#endif

namespace chip {
namespace DeviceLayer {

using namespace chip::TLV;

union SoftwareUpdateManager::InEventParam
{
    void Clear() { memset(this, 0, sizeof(*this)); }

    SoftwareUpdateManager * Source;
    struct
    {
        TLVWriter * MetaDataWriter;
    } PrepareQuery_Metadata;

    struct
    {
        CHIP_ERROR Error;
    } QueryPrepareFailed;

    struct
    {
        uint8_t IntegrityType;
        const char * URI;
        const char * Version;
    } SoftwareUpdateAvailable;

    struct
    {
        const char * URI;
    } FetchPartialImageInfo;

    struct
    {
        const char * URI;
        uint8_t IntegrityType;
    } PrepareImageStorage;

    struct
    {
        uint8_t * DataBlock;
        uint32_t DataBlockLen;
    } StoreImageBlock;

    struct
    {
        uint8_t IntegrityType;
        uint8_t * IntegrityValueBuf;  // Pointer to the buffer for the app to copy Integrity Value into.
        uint8_t IntegrityValueBufLen; // Length of the provided buffer.
    } ComputeImageIntegrity;

    struct
    {
        CHIP_ERROR Error;
    } Finished;
};

union SoftwareUpdateManager::OutEventParam
{
    void Clear() { memset(this, 0, sizeof(*this)); }

    bool DefaultHandlerCalled;
    struct
    {
        const char * PackageSpecification;
        const char * DesiredLocale;
        CHIP_ERROR Error;
    } PrepareQuery;

    struct
    {
        CHIP_ERROR Error;
    } PrepareQuery_Metadata;

    struct
    {
        ActionType Action;
    } SoftwareUpdateAvailable;

    struct
    {
        uint64_t PartialImageLen;
    } FetchPartialImageInfo;

    struct
    {
        CHIP_ERROR Error;
    } StoreImageBlock;

    struct
    {
        CHIP_ERROR Error;
    } ComputeImageIntegrity;
};

inline CHIP_ERROR SoftwareUpdateManager::Init()
{
    return static_cast<ImplClass *>(this)->_Init();
}

inline CHIP_ERROR SoftwareUpdateManager::CheckNow()
{
    return static_cast<ImplClass *>(this)->_CheckNow();
}

inline CHIP_ERROR SoftwareUpdateManager::ImageInstallComplete(CHIP_ERROR aError)
{
    return static_cast<ImplClass *>(this)->_ImageInstallComplete(aError);
}

inline CHIP_ERROR SoftwareUpdateManager::PrepareImageStorageComplete(CHIP_ERROR aError)
{
    return static_cast<ImplClass *>(this)->_PrepareImageStorageComplete(aError);
}

inline SoftwareUpdateManager::State SoftwareUpdateManager::GetState()
{
    return static_cast<ImplClass *>(this)->_GetState();
}

inline CHIP_ERROR SoftwareUpdateManager::Abort()
{
    return static_cast<ImplClass *>(this)->_Abort();
}

inline bool SoftwareUpdateManager::IsInProgress()
{
    return static_cast<ImplClass *>(this)->_IsInProgress();
}

inline CHIP_ERROR SoftwareUpdateManager::SetQueryIntervalWindow(uint32_t aMinRangeSecs, uint32_t aMaxRangeSecs)
{
    return static_cast<ImplClass *>(this)->_SetQueryIntervalWindow(aMinRangeSecs, aMaxRangeSecs);
}

inline void SoftwareUpdateManager::SetRetryPolicyCallback(const RetryPolicyCallback aRetryPolicyCallback)
{
    static_cast<ImplClass *>(this)->_SetRetryPolicyCallback(aRetryPolicyCallback);
}

inline CHIP_ERROR SoftwareUpdateManager::SetEventCallback(void * const aAppState, const EventCallback aEventCallback)
{
    return static_cast<ImplClass *>(this)->_SetEventCallback(aAppState, aEventCallback);
}

inline void SoftwareUpdateManager::DefaultEventHandler(void * apAppState, EventType aEvent, const InEventParam & aInParam,
                                                       OutEventParam & aOutParam)
{
    ImplClass::_DefaultEventHandler(apAppState, aEvent, aInParam, aOutParam);
}

} // namespace DeviceLayer
} // namespace chip

#endif // CHIP_DEVICE_CONFIG_ENABLE_SOFTWARE_UPDATE_MANAGER
